﻿using System;
using System.Collections.Generic;
using System.Text;
using Algoritmia.DataStructures.Maps;
using Algoritmia.Utils;

namespace Algoritmia.DataStructures.Digraphs
{
    public class InverseAdjacencyDigraph<T> : AdjacencyDigraph<T>
    {
        private IMap<T, Sets.ISet<T>> _preds;

        public InverseAdjacencyDigraph(IEnumerable<T> vertices=null,
                                       IEnumerable<Tuple<T,T>> edges=null,
                                       bool directed=true, EqualityComparison<T> equalityComparison = null,
                                       Func<IEnumerable<T>, IMap<T, Sets.ISet<T>>> createMap = null,
                                       Func<IEnumerable<T>, Sets.ISet<T>> createSet = null, 
                                       Func<IMap<T, Sets.ISet<T>>, T, IMap<T, Sets.ISet<T>>> redimensionMap = null,
                                       Func<Sets.ISet<T>, T, Sets.ISet<T>> redimensionSet = null)
            : base(vertices,edges,directed,equalityComparison,createMap,createSet,redimensionMap,redimensionSet)
        {
            BuildInverseAdjacencyDigraph();
        }

        public InverseAdjacencyDigraph( IEnumerable<KeyValuePair<T, IEnumerable<T>>> edges ,
                                       bool directed = true, EqualityComparison<T> equalityComparison = null,
                                       Func<IEnumerable<T>, IMap<T, Sets.ISet<T>>> createMap = null,
                                       Func<IEnumerable<T>, Sets.ISet<T>> createSet = null,
                                       Func<IMap<T, Sets.ISet<T>>, T, IMap<T, Sets.ISet<T>>> redimensionMap = null,
                                       Func<Sets.ISet<T>, T, Sets.ISet<T>> redimensionSet = null)
            : base(edges, directed, equalityComparison, createMap, createSet, redimensionMap, redimensionSet)
        {
        }

        private void BuildInverseAdjacencyDigraph()
        {
            if (_directed)
            {
                _preds = _createMap(V);
                foreach (T v in _succs.Keys())
                {
                    _preds[v] = _createSet(V);
                }
                foreach (var edge in E)
                {
                    _preds[edge.Item2].Add(edge.Item1);
                }
            }
            else
            {
                _preds = _succs;
            }
        }

        public override int InDegree(T vertex)
        {
            return _preds[vertex].Count;
        }

        public override IEnumerable<T> Predecessors(T vertex)
        {
            return _preds[vertex];
        }

        protected override void AddVertex(T vertex)
        {
            if (!_succs.Contains(vertex))
            {
                _succs = _redimensionMap(_succs, vertex);
                _succs[vertex] = _createSet(V);

                if (!_directed)
                {
                    _preds = _redimensionMap(_preds, vertex);
                    _preds[vertex] = _createSet(V);
                }
            }
        }

        protected override bool RemoveVertex(T vertex)
        {
            if (_succs.Remove(vertex))
            {
                foreach (var key in _succs.Keys())
                {
                    _succs[key].Discard(vertex);
                }
                if (_directed)
                {
                    _preds.Remove(vertex);
                    foreach(var key in _preds.Keys())
                    {
                        _preds[key].Discard(vertex);
                    }
                }
                return true;
            }
            return false;
        }

        protected override void AddEdge(T vertex1, T vertex2)
        {
            if (!_succs.Contains(vertex1))
            {
                _succs = _redimensionMap(_succs, vertex1);
                _succs[vertex1] = _createSet(V);
                if (_directed)
                {
                    _preds = _redimensionMap(_preds, vertex1);
                    _preds[vertex1] = _createSet(V);
                }
            }
            _succs[vertex1] = _redimensionSet(_succs[vertex1], vertex2);
            _succs[vertex1].Add(vertex2);

            if (!_succs.Contains(vertex2))
            {
                _succs = _redimensionMap(_succs, vertex2);
                _succs[vertex2] = _createSet(V);
                if (_directed)
                {
                    _preds = _redimensionMap(_preds, vertex2);
                    _preds[vertex2] = _createSet(V);
                }
            }
            if (!_directed)
            {
                _succs[vertex2] = _redimensionSet(_succs[vertex2], vertex1);
                _succs[vertex2].Add(vertex1);
            }
            else
            {
                _preds[vertex2] = _redimensionSet(_preds[vertex2], vertex1);
                _preds[vertex2].Add(vertex1);
            }
        }

        protected override bool RemoveEdge(T vertex1, T vertex2)
        {
            if (!_succs.Contains(vertex1) || !_succs.Contains(vertex2))
            {
                return false;
            }
            if (!_directed)
            {
                _succs[vertex2].Remove(vertex1);
            }
            else
            {
                _preds[vertex2].Remove(vertex1);
            }
            return _succs[vertex1].Remove(vertex2);
        }

        public override string ToString()
        {
            var sb = new StringBuilder(String.Format("{0} <{1}> [ ", GetType().Name, typeof(T).Name));

            foreach (var kv in _succs)
            {
                sb.Append(String.Format("( {0} , {1} ) ,", kv.Key, kv.Value));
            }
            sb.Append("]");
            return sb.ToString();
        }
    }
}
